﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace MyLib {
	/// <summary>
	/// 
	/// </summary>
	public static class ObjectDumper {
		/// <summary>
		/// 
		/// </summary>
		private class DumpWriter {
			private const string IndentString = "  ";

			private TextWriter m_writer;
			private bool m_isNewLine;
			private int m_level;
			private string m_prefix;

			private Stack<string> m_prefixStack;

			/// <summary>
			/// 
			/// </summary>
			public string Prefix {
				get {
					return m_prefix;
				}
				set {
					if (value == null) {
						m_prefix = "";
					} else {
						m_prefix = value;
					}
				}
			}

			/// <summary>
			/// 
			/// </summary>
			public void Indent() {
				m_level++;
				m_prefixStack.Push(m_prefix);
				m_prefix = "";
			}

			/// <summary>
			/// 
			/// </summary>
			public void UnIndent() {
				m_level--;
				m_prefix = m_prefixStack.Pop();
			}

			private bool m_dumpingObject = false;

			/// <summary>
			/// 
			/// </summary>
			public void EnterObject() {
				System.Diagnostics.Debug.Assert(m_dumpingObject == false);

				m_dumpingObject = true;
			}

			/// <summary>
			/// 
			/// </summary>
			public void ExitObject() {
				System.Diagnostics.Debug.Assert(m_dumpingObject == true);

				m_dumpingObject = false;
			}

			/// <summary>
			/// 
			/// </summary>
			public bool IsDumpingObject {
				get {
					return m_dumpingObject;
				}
			}

			/// <summary>
			/// 
			/// </summary>
			/// <param name="format"></param>
			/// <param name="objArray"></param>
			public void Dump(string format, params object[] objArray) {
				if (!m_isNewLine) {
					m_writer.WriteLine();
				}

				for (int i = 0; i < m_level; ++i) {
					m_writer.Write(IndentString);
				}

				m_writer.Write(m_prefix);

				if (objArray == null || objArray.Length == 0) {
					m_writer.Write(format);
				} else {
					m_writer.Write(format, objArray);
				}

				m_isNewLine = false;
			}

			/// <summary>
			/// 
			/// </summary>
			public void Close() {
				m_writer.Flush();
				m_writer.Close();
			}

			/// <summary>
			/// 
			/// </summary>
			/// <param name="writer"></param>
			public DumpWriter(TextWriter writer) {
				System.Diagnostics.Debug.Assert(writer != null);

				m_level = 0;
				m_isNewLine = true;
				m_prefix = "";
				m_writer = writer;
				m_prefixStack = new Stack<string>();
			}
		} // end of DumpWriter.

		/// <summary>
		/// 
		/// </summary>
		/// <param name="v"></param>
		/// <returns></returns>
		public static string Dump(object v) {
			var sb = new System.Text.StringBuilder();

			Dump(v, new StringWriter(sb));

			return sb.ToString();
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="v"></param>
		/// <param name="writer"></param>
		public static void Dump(object v, TextWriter writer) {
			System.Diagnostics.Debug.Assert(writer != null);

			var dw = new DumpWriter(writer);
			try {
				Dump(v, dw);
			} finally {
				dw.Close();
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="v"></param>
		/// <param name="writer"></param>
		private static void Dump(object v, DumpWriter writer) {
			if (v == null) {
				writer.Dump("null");
				return;
			}

			var vt = v.GetType();

			if (vt == typeof(string)) {
				var sv = (string)v;
				writer.Dump("(String, Length:{0}) \"{1}\"", sv.Length, sv.Replace("\"", "\\\""));
			} else if (vt.IsValueType) {
				writer.Dump(string.Format("({0}) {1}", vt.Name, v));
			} else if (vt.IsArray) {
				var ary = (Array)v;
				writer.Dump("(Array of {0}, Count:{1}, Rank:{2})", vt.GetElementType().Name, ary.LongLength, ary.Rank);
				if (ary.LongLength != 0L) {
					writer.Indent();
					for (long i = 0L; i < ary.LongLength; ++i) {
						writer.Prefix = string.Format("{0} : ", i);
						Dump(ary.GetValue(i), writer);
					}
					writer.UnIndent();
				}
			} else if (v is ICollection) {
				var list = (ICollection)v;
				writer.Dump("({0}, Count:{1})", vt, list.Count);
				if (list.Count != 0) {
					writer.Indent();
					var i = 0;
					foreach (var item in list) {
						writer.Prefix = string.Format("{0} : ", i++);
						Dump(item, writer);
					}
					writer.UnIndent();
				}
			} else if (v is IEnumerable) {
				var enu = (IEnumerable)v;
				writer.Dump("({0})", vt);
				var iter = enu.GetEnumerator();
				if (iter != null) {
					writer.Indent();
					for (int i = 0; iter.MoveNext(); ++i) {
						writer.Prefix = string.Format("{0} : ", i);
						Dump(iter.Current, writer);
					}
					writer.UnIndent();
				}
			} else {
				var dpn = "Item"; // Default property name.
				var dna = vt.GetCustomAttributes(typeof(System.Reflection.DefaultMemberAttribute), true)
					.Cast<System.Reflection.DefaultMemberAttribute>()
					.FirstOrDefault();
				if (dna != null) {
					dpn = dna.MemberName;
				}

				if (writer.IsDumpingObject) {
					writer.Dump("({0}) \"{1}\"", vt, v.ToString());
					return;
				} else {
					writer.Dump("({0})", vt);
				}

				writer.EnterObject();
				writer.Indent();
				foreach (var p in vt.GetProperties()) {
					if (!p.CanRead || p.IsSpecialName) {
						continue;
					}
					if (p.Name == dpn) {
						// Skip indexer property.
						continue;
					}

					writer.Prefix = string.Format("{0} : ", p.Name);
					object pcv = null;
					try {
						pcv = p.GetValue(v, null);
					} catch (Exception pex) {
						var iex = pex;
						while (iex.InnerException != null) {
							iex = iex.InnerException;
						}

						writer.Dump("{0} \"{1}\"", iex.GetType(), iex.Message);
						continue;
					}
					Dump(pcv, writer);
				}
				writer.UnIndent();
				writer.ExitObject();
			}
		} // end of Dump.
	} // end of ObjectDumper.
}
